/*
 * RenderTests.cpp
 *
 *  Created on: 09.08.2011
 *      Author: Blinov Kirill
 *      Copyright: LGPL
 */
#include <iostream>
#include <string.h>
#include <cstddef>
#include <stdlib.h>
#include <boost/thread.hpp>
#include <boost/function.hpp>
#include <SDL/SDL.h>

#include "Visualisator/CVisualisator.h"
#include "Visualisator/Camera.h"

#include "Manager Resource/IUnit.h"
#include "Manager Resource/UnitFabric.h"

#include "Constants.h"
#include "RenderTests.h"
#include "GUI/GUIInit.h"
#include "StructSource.h"

struct SmdModel
{
	STANDART_VERTEX* vertices;
	GLushort *indexes;
	UINT vertexCount;
	GLsizei triangleCount;

	SmdModel():vertexCount(0),triangleCount(0){};
	virtual ~SmdModel(){
							if ( NULL != vertices ) delete[] vertices;
							if ( NULL != indexes ) delete[] indexes;
					   };
};

int            IncomeNumber;
volatile bool  Condition;

TThreadSafeQueue<FULL_FRAME_DATA*> FrameQueue;
TThreadSafeQueue<INPUT_MESSAGE*>   InputMessageQueue;
boost::thread                     *RenderRun;

CGameCamera                MainCamera; //Default parametrs are suitable
std::vector<IUnit*>        GameUnitsArray;
std::vector<GUI_ELEMENT*>  GuiElementsArray;
glm::mat4                  ProjectionMatrix;
GLuint                     VertexArrayId;
RENDER_MODEL_INFO          GuiDummy;
USHORT                     PrevLeftButtonState = MOUSE_KEY_UP;

CVisualisator* Visualisator;

CManagerGUI Manager;
const std::string UNIT_TEMPLATE_NAME = "SimpleHeavyJet";
const std::string UNIT_NAME = "FirstJet";

void
CalculateGabarits( const SmdModel& Model, glm::vec4 &outSphere, glm::vec3 &outFirstCorner, glm::vec3 &outSecondCorner )
{
	outFirstCorner = glm::vec3(0); // minimal values
	outSecondCorner = glm::vec3(0); // maximal values
	outSphere = glm::vec4(0);

	//Find dimensions
	for ( UINT i = 0; i < Model.vertexCount; i++ )
	{
		STANDART_VERTEX WorkVertex = Model.vertices[i];
		if ( WorkVertex.position.x > outSecondCorner.x )
		{
			outSecondCorner.x = WorkVertex.position.x;
		}
		else if ( WorkVertex.position.x < outFirstCorner.x )
		{
			outFirstCorner.x = WorkVertex.position.x;
		}

		if ( WorkVertex.position.y > outSecondCorner.y )
		{
			outSecondCorner.y = WorkVertex.position.y;
		}
		else if ( WorkVertex.position.y < outFirstCorner.y )
		{
			outFirstCorner.y = WorkVertex.position.y;
		}

		if ( WorkVertex.position.z > outSecondCorner.z )
		{
			outSecondCorner.z = WorkVertex.position.z;
		}
		else if ( WorkVertex.position.z < outFirstCorner.z )
		{
			outFirstCorner.z = WorkVertex.position.z;
		}
	}

	glm::vec3 Radius = ( outSecondCorner - outFirstCorner ) / 2.0f;
	outSphere = glm::vec4( Radius, glm::length( Radius ) );
}

STANDART_VERTEX
ParseVertex( const char* VertString )
{
	STANDART_VERTEX Result;
	int TempNull;
	float TempNullF;

	sscanf( VertString, "%d %f %f %f %f %f %f %f %f %d %d %f", &TempNull,
			&Result.position.x, &Result.position.y, &Result.position.z,
			&Result.normal.x, &Result.normal.y, &Result.normal.z,
			&Result.texture.x, &Result.texture.y,
			&TempNull, &TempNull, &TempNullF);

	return Result;
}

SmdModel*
CompressBuffersToModel( const STANDART_VERTEX *Vertices, const UINT VertCount )
{
	SmdModel* Result = new SmdModel();
	Result->triangleCount = VertCount / 3;
	Result->indexes = new GLushort[VertCount];

	memset( (void*)( Result->indexes ), 0xFFFF, size_t( sizeof(GLushort) * VertCount ) );

	STANDART_VERTEX CompressedVertices[VertCount];
	GLushort CurrentIndex = 0;

	for ( UINT i = 0; i < VertCount; i++ )
	{
        if ( 0xFFFF == Result->indexes[i] )
        {
        	STANDART_VERTEX tmpVertex = Vertices[i];
        	Result->indexes[i] = CurrentIndex;
        	for ( UINT j = (i+1); j < VertCount; j++ )
        	{
        		if (( Vertices[j].position.x == tmpVertex.position.x ) &&
        			( Vertices[j].position.y == tmpVertex.position.y ) &&
        			( Vertices[j].position.z == tmpVertex.position.z ))
        		{
        			Result->indexes[j] = CurrentIndex;
        		}
        	}
        	CompressedVertices[CurrentIndex] = tmpVertex;
        	CurrentIndex++;
        }
	}

	Result->vertices = new STANDART_VERTEX[CurrentIndex];
	Result->vertexCount = CurrentIndex;

	memcpy( (void*)Result->vertices, (void*)CompressedVertices, sizeof(STANDART_VERTEX) * CurrentIndex );

	return Result;
}

SmdModel*
LoadSMDModel( const char* FileName )
{
	SmdModel* Result = NULL;

	char buffer[1024];
	STANDART_VERTEX* TempVertex = NULL;
	UINT TriangleCount = 0;

	FILE *file = fopen(FileName,"rt");

	if (file != NULL)
	while (fgets(buffer,1023,file))
	{
		buffer[strlen(buffer) - OFFSET_FROM_LINE_END] = '\0';
		if ((strcmp(buffer,"triangles") == 0) && (0 == TriangleCount))
			while (fgets(buffer,1023,file))
			{
				buffer[strlen(buffer) - OFFSET_FROM_LINE_END] = '\0';
				if(strcmp(buffer,"end") == 0)
				{
					TempVertex = new STANDART_VERTEX[TriangleCount*3];
					rewind(file);
					break;
				}
				fgets(buffer,1023,file);
				fgets(buffer,1023,file);
				fgets(buffer,1023,file);
				TriangleCount++;
			}
		//--------------------------------------
		if ( (strcmp(buffer,"triangles") == 0) && ( TempVertex != NULL ) )
		{
			for (UINT i = 0; i < TriangleCount ;i++)
			{
				fgets(buffer,1023,file);

				for ( int j = 0; j < 3; j++ )
				{
					fgets(buffer,1023,file);
					TempVertex[i*3+j] = ParseVertex( buffer );
				}
			}

			Result = CompressBuffersToModel( TempVertex, TriangleCount * 3 );
		}
	}
	if ( NULL != TempVertex )
	    delete[] TempVertex;

	fclose(file);
	file = NULL;
	return Result;
}

void
FirstRenderTest()
{
	fprintf( stderr, "--FirstRenderTest\n" );
	while( Condition )
	{
		std::vector<GAME_OBJECT*> GameObjectsArray;

		//Allocate memory for GAME_OBJECT-s
		GameObjectsArray.reserve( GameUnitsArray.size() );
		for ( std::vector<IUnit*>::const_iterator BeginIt = GameUnitsArray.begin(),
			  FinishIt = GameUnitsArray.end();
			  BeginIt != FinishIt; ++BeginIt )
		{
			IUnit* CurrentUnit = *BeginIt.base();
			GameObjectsArray.push_back( CurrentUnit->GetVisualInfo() );
		}

		FULL_FRAME_DATA *FrameData =  new FULL_FRAME_DATA();
		FrameData->guiDummy =             &GuiDummy;
		FrameData->vertexArrayId =        VertexArrayId;
		FrameData->gameObjectsArray =     GameObjectsArray;
		FrameData->camerasArray     =     std::vector<GAME_CAMERA*>( 1, MainCamera.GetCurrentCamera() );

		FrameData->guiElementsArray =	  Manager.Draw();

		FrameQueue.Send( FrameData );
		boost::this_thread::sleep( boost::posix_time::milliseconds( RENDER_DEFAULT_WAIT_TIME ) );

		//Process messages
		INPUT_MESSAGE* InputMessage;
		while ( InputMessageQueue.Receive( &InputMessage ) )
		{
			if ( InputMessage->terminateApplication )
			{
				Condition = false;
			}

			/*bool MouseClickHappend = false;
			if ( PrevLeftButtonState != InputMessage->mouseKeys[MOUSE_BUTTON_LEFT] )
			{
				PrevLeftButtonState = InputMessage->mouseKeys[MOUSE_BUTTON_LEFT];
				Manager.EventMouse( PrevLeftButtonState );
				MouseClickHappend = true;
			}*/

			if ( ( InputMessage->mouseDelta.x != 0 ) ||
				 ( InputMessage->mouseDelta.y != 0 ) ||
				 ( PrevLeftButtonState != InputMessage->mouseKeys[MOUSE_BUTTON_LEFT] ) )
			{
				glm::vec2 t;
				t.x = float(InputMessage->mousePosition.x) / float(WINDOW_WIDTH);
				t.y = ( 1.0f - float(InputMessage->mousePosition.y) / float(WINDOW_HEIGHT) );
				PrevLeftButtonState = InputMessage->mouseKeys[MOUSE_BUTTON_LEFT];
				Manager.EventMouse( PrevLeftButtonState, t );
			}

			for ( std::vector<USHORT>::const_iterator it = InputMessage->keys.begin(),
				  end = InputMessage->keys.end();
				  it != end; ++it)
			{
				USHORT Key = *it.base();
				if ( 'q' == Key )
				{
					Condition = false;
				}
			}
			delete InputMessage;
		}
	}

	Visualisator->StopVisualisation();
}

int
Mmain( )
{
	//Fill GUI dummy
	fprintf( stderr, "Start execution test\n" );
	fprintf( stderr, "Start visualisator component tests\n" );

	//Setup dimension
	Visualisator = new CVisualisator();
	SCREEN_DIMENSION Dimension;
	Dimension.width = WINDOW_WIDTH;
	Dimension.height = WINDOW_HEIGHT;
	Dimension.multisampleBufferCount = 4;
	Dimension.sdlFlags = SDL_HWSURFACE | SDL_OPENGL;

	fprintf( stderr, "Init window\n" );
	//Init window
	Visualisator->SetDimension( Dimension );
	Visualisator->ConfigVisualisation(  );

	CManagerResource* MResource;
	MResource = new CManagerResource();

	//-------------GUI---------------------------
	//CManagerGUI Manager;
	Manager.Init( "./ShablonsGUI/TestShablon.txt", "./ShablonsGUI/MapGUI.txt" );
	glm::vec2 f;

	CLabel* Label1 = Manager.Shablon->ShablonLabel[0];
	Label1 = new CLabel( *( Label1 ) );
	Label1->SetName("Label1");//FIXME Memory leak
	f.x = 0.1;
	f.y = 0.1;
	Label1->SetPosition(f);

	CEdit* Edit1 = Manager.Shablon->ShablonEdit[0];
	Edit1 = new CEdit( *( Edit1 ) );
	Edit1->SetName("Edit1");//FIXME Memory leak
	f.x = 0.1;
	f.y = 0.25;
	Edit1->SetPosition(f);

	CMemo* Memo1 = Manager.Shablon->ShablonMemo[0];
	Memo1 = new CMemo( *( Memo1 ) );
	Memo1->SetName("Memo1");//FIXME Memory leak
	f.x = 0.1;
	f.y = 0.40;
	Memo1->SetPosition(f);

	CListBox* ListBox1 = Manager.Shablon->ShablonListBox[0];
	ListBox1 = new CListBox( *( ListBox1 ) );
	ListBox1->SetName("ListBox1");//FIXME Memory leak
	f.x = 0.1;
	f.y = 0.55;
	ListBox1->SetPosition(f);

	CComboBox* ComboBox1 = Manager.Shablon->ShablonComboBox[0];
	ComboBox1 = new CComboBox( *( ComboBox1 ) );
	ComboBox1->SetName("ComboBox1");//FIXME Memory leak
	f.x = 0.1;
	f.y = 0.70;
	ComboBox1->SetPosition(f);

	CButton* Button1 = Manager.Shablon->ShablonButton[0];
	Button1 = new CButton( *( Button1 ) );
	Button1->SetName("Button1");//FIXME Memory leak
	f.x = 0.1;
	f.y = 0.85;
	Button1->SetPosition(f);

	CCheckBox* CheckBox1 = Manager.Shablon->ShablonCheckBox[0];
	CheckBox1 = new CCheckBox( *( CheckBox1 ) );
	CheckBox1->SetName("CheckBox1");//FIXME Memory leak
	f.x = 0.25;
	f.y = 0.1;
	CheckBox1->SetPosition(f);

	CRadioButton* RadioButton1 = Manager.Shablon->ShablonRadioButton[0];
	RadioButton1 = new CRadioButton( *( RadioButton1 ) );
	RadioButton1->SetName("RadioButton1");//FIXME Memory leak
	f.x = 0.25;
	f.y = 0.25;
	RadioButton1->SetPosition(f);

	CProgressBar* ProgressBar1 = Manager.Shablon->ShablonProgressBar[0];
	ProgressBar1 = new CProgressBar( *( ProgressBar1 ) );
	ProgressBar1->SetName("ProgressBar1");//FIXME Memory leak
	f.x = 0.25;
	f.y = 0.40;
	ProgressBar1->SetPosition(f);

	CTrackBar* TrackBar1 = Manager.Shablon->ShablonTrackBar[0];
	TrackBar1 = new CTrackBar( *( TrackBar1 ) );
	TrackBar1->SetName("TrackBar1");//FIXME Memory leak
	f.x = 0.25;
	f.y = 0.55;
	TrackBar1->SetPosition(f);

	CPanel* Panel1 = Manager.Shablon->ShablonPanel[0];
	Panel1 = new CPanel( *( Panel1 ) );
	Panel1->SetName("Panel1");//FIXME Memory leak
	f.x = 0.25;
	f.y = 0.70;
	Panel1->SetPosition(f);

	CInventar* Inventar1 = Manager.Shablon->ShablonInventar[0];
	Inventar1 = new CInventar( *( Inventar1 ) );
	Inventar1->SetName("Inventar1");//FIXME Memory leak
	f.x = 0.25;
	f.y = 0.85;
	Inventar1->SetPosition(f);

	CPanel* Panel2 = Manager.Shablon->ShablonPanel[1];
	Panel2 = new CPanel( *( Panel2 ) );
	Panel2->SetName("Panel2");//FIXME Memory leak
	f.x = 0.40;
	f.y = 0.25;
	Panel2->SetPosition(f);

	CButton* Button2 = Manager.Shablon->ShablonButton[1];
	Button2 = new CButton( *( Button2 ) );
	Button2->SetName("Button2");//FIXME Memory leak
	f.x = 0.48;
	f.y = 0.60;
	Button2->SetPosition(f);

	CButton* Button3 = Manager.Shablon->ShablonButton[2];
	Button3 = new CButton( *( Button3 ) );
	Button3->SetName("Button3");//FIXME Memory leak
	f.x = 0.48;
	f.y = 0.50;
	Button3->SetPosition(f);


	CTreeIterator* Iter = Manager.OwnerIterator->GetIterator();
	bool Res;
	Res = Manager.AddControl("", Label1);
	Res = Manager.AddControl("", Edit1);
	Res = Manager.AddControl("", Memo1);
	Res = Manager.AddControl("", ListBox1);
	Res = Manager.AddControl("", ComboBox1);
	Res = Manager.AddControl("", Button1);
	Res = Manager.AddControl("", CheckBox1);
	Res = Manager.AddControl("", RadioButton1);
	Res = Manager.AddControl("", ProgressBar1);
	Res = Manager.AddControl("", TrackBar1);
	Res = Manager.AddControl("", Panel1);
	Res = Manager.AddControl("", Inventar1);
	Res = Manager.AddControl("", Panel2);
/*	Res = Manager.AddControl("Panel2", Button2);
	Res = Manager.AddControl("Panel2", Button3);*/
	//-------------------------------------------

	//TestInit();
	fprintf( stderr, "Load model and shaders\n" );
	SmdModel* LoadedModel = LoadSMDModel("./models/sphere.SMD");
	SmdModel* SmdGui      = LoadSMDModel("./models/dummy.SMD");
	UINT TextureWidth, TextureHeight;
	GLuint TextureName = MResource->SetUpTexture2D( std::string("./textures/cat_3.dds"), TextureWidth, TextureHeight );

	//Load common Shader
	GLuint VertexShaderName = Visualisator->SetUpShader( "./shaders/VertexShader.vert" );
	GLuint FragmentShaderName = Visualisator->SetUpShader( "./shaders/FragmentShader.frag" );

	fprintf(stdout, "Shaders names: %u %u\n", VertexShaderName, FragmentShaderName );
	if ( Visualisator->LinkShaderProgramm( VertexShaderName, FragmentShaderName, SHADER_TYPE_SIMPLE_LIGHTING_NO_SHADOW ) != true )
	{
		fprintf( stderr, "Linking shaders is failed\n" );
		return 0;
	}

	//Load shader for GUI
	VertexShaderName = Visualisator->SetUpShader( "./shaders/GuiVertexShader.vert" );
	FragmentShaderName = Visualisator->SetUpShader( "./shaders/GuiFragmentShader.frag" );
	fprintf(stdout, "Shaders names: %u %u\n", VertexShaderName, FragmentShaderName );
	if ( Visualisator->LinkShaderProgramm( VertexShaderName, FragmentShaderName, SHADER_TYPE_GUI ) != true )
	{
		fprintf( stderr, "Linking GUI shaders is failed\n" );
		return 0;
	}

	fprintf( stderr, "Create Game object1\n" );
	//Create Game object
	RENDER_MODEL_INFO* Model = new RENDER_MODEL_INFO( SHADER_TYPE_SIMPLE_LIGHTING_NO_SHADOW, LoadedModel->vertexCount,
													  std::vector<GLushort> ( LoadedModel->indexes, LoadedModel->indexes +
															                  ( LoadedModel->triangleCount * 3 ) )
												    );
	fprintf( stderr, "Create Game object after\n" );

	GuiDummy.shaderType = SHADER_TYPE_GUI;
	GuiDummy.vertexCount = SmdGui->vertexCount;
	GuiDummy.indexes = std::vector<GLushort> ( SmdGui->indexes, SmdGui->indexes + ( SmdGui->triangleCount * 3 ) );
	GuiDummy.indexType = GL_UNSIGNED_SHORT;
	GuiDummy.primitiveType = GL_TRIANGLES;

	glm::mat4 InstanceMatrix(1.0f);
	InstanceMatrix = glm::translate( InstanceMatrix, glm::vec3( 0.0f, 0.0f, -0.5f ));
	GAME_SUBOBJECT_INSTANCE *ModelInstance = new GAME_SUBOBJECT_INSTANCE( Model, InstanceMatrix, std::vector<GLuint> ( 1, TextureName ) );

	SUBOBJECT_WITH_CONTROLLER SubobjectWithController( ModelInstance, NULL );
	GAME_OBJECT* FirstGameObject = new GAME_OBJECT();
	FirstGameObject->models = std::vector<SUBOBJECT_WITH_CONTROLLER>( 1, SubobjectWithController );
	FirstGameObject->worldMatrix = glm::mat4(1.0f);
	glm::vec4 Sphere;
	CalculateGabarits( (*LoadedModel), Sphere, FirstGameObject->firstCorner, FirstGameObject->secondCorner );
	FirstGameObject->sphereCenter = glm::vec3(Sphere);
	FirstGameObject->sphereRadius = Sphere.w;

	//Create a game object template
	UnitFabric::AddTemplate( UNIT_TYPE_HEAVY_JET, UNIT_TEMPLATE_NAME, FirstGameObject );
	IUnit* HeavyJet;
	UnitFabric::CreateUnit( UNIT_PHYSIC_PARAMS(), UNIT_TEMPLATE_NAME, UNIT_NAME, &HeavyJet );
	GameUnitsArray.push_back( HeavyJet );

	fprintf( stderr, "Load models to OGL\n" );
    //Load models to OGL

	std::vector<PURE_COMPRESSED_MODEL> CompressedModels;
	PURE_COMPRESSED_MODEL CompressedModel( std::vector<STANDART_VERTEX> ( LoadedModel->vertices, LoadedModel->vertices +
			                                                              ( LoadedModel->vertexCount ) ),
										   std::vector<GLushort> ( LoadedModel->indexes, LoadedModel->indexes + ( LoadedModel->triangleCount * 3 ) ),
										   std::vector<GLuint> ( 1, TextureName )
										 );
	CompressedModel.renderInfo = Model;

	PURE_COMPRESSED_MODEL CompressedGui  ( std::vector<STANDART_VERTEX> ( SmdGui->vertices, SmdGui->vertices +
			                                                              ( SmdGui->vertexCount ) ),
										   std::vector<GLushort> ( SmdGui->indexes, SmdGui->indexes + ( SmdGui->triangleCount * 3 ) ),
										   std::vector<GLuint> ( 1, 0 )
										 );
	CompressedGui.renderInfo = &GuiDummy;

	CompressedModels.push_back( CompressedModel );
	CompressedModels.push_back( CompressedGui );

	VertexArrayId = Visualisator->SetUpModels( CompressedModels );

	//Start visualisation test
	Condition = true;
	RenderRun = new boost::thread( boost::bind( FirstRenderTest ) );

	fprintf( stderr, "StartVisualisation\n" );
	Visualisator->StartVisualisation( &FrameQueue, &InputMessageQueue );

    Condition = false;
    RenderRun->join();
	return 0;
}
